/* want code easy to audit so we don't include external libraries. */

var DOMAIN_BASED_VARIABLES = {
  'DEFAULT' : {
    // hard character set.
    'charset' : 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*_-+=',
    'length' : 14
  },

  'LESS_SECURE' : {
    'charset' : 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',
    'length' : 8
  },

  // search start at index = 2
  'americanexpress.com' : {
    'charset' : 'ABCDEFGHIJKLMNOP0123456789%&_?#=-',
    'length' : 19
  }

  // insert new items here!
};

/**
 * Looks to see if domain is special case, if not then default is returned.
 * @param domain {string} cleaned up domain string like 'apple.com' or 'LESS_SECURE'
 * @return {json object} see data above, but result['charset'] and result['length'] are in there.
 */
function loadDataBasedOnDomain(domain) {
  var data = DOMAIN_BASED_VARIABLES[domain];
  if (data) { 
   return data;
  } else {
    return DOMAIN_BASED_VARIABLES['DEFAULT'];
  }
}


function isNumber(n) {
  return !isNaN(parseFloat(n)) && isFinite(n);
}

/**
 * functions to give a consistent sequence of random numbers
 */
var g_seqseed = 0;
function init_sequentialRandom(start_seed) {
  g_seqseed = start_seed;
}

/**
 * Call init_sequentialRandom() first. Return an consistent random number based on seed
 * @return {Number}
 */
function sequentialRandom() {
  var seed = (g_seqseed * 9301 + 49297) % 233280;
  var rnd = seed / 233280.0;
  g_seqseed = seed;
  return rnd
}

function sequentialRandomRange(min, max) {
  max = max || 1;
  min = min || 0;

  return min + sequentialRandom() * (max - min);
}

/* hash: compute hash value of string
 * see: http://goo.gl/5RDHS
 * 32 bit has is good enough. it's used as a seed to a Rnd()
 * */
function hash32(str) {
  var MULTIPLIER = 33;    // perl uses
  var h = 0;
  var len = str.length;

  for (var ii = 0; ii < len; ii++) {
    var char_p = str.charCodeAt(ii);
    h = MULTIPLIER * h + char_p;
    // OK. So don't be fooled by javascript.
    // we only have limited bits of accuracy even though MAXINT is 1.7976931348623157e+308
    h = h % 0xFFFFFFFF;
  }
  h += (h >> 5);  // improve distribution of lower-order bits
  return h;
}

/* given a full url, pull out the domain bit */
function parseDomain(url) {
    //Regex expressions are so unreadable. would be nice if chrome exposed this functionality
  var matches = url.match(/^https?\:\/\/([^\/:?#]+)(?:[\/:?#]|$)/i);
  var domain = matches && matches[1];  // domain will be null if no match is found
  if (domain) {
    return domain;
  }

  // fallback logic
  var arr = url.split("/");
  if (arr[1] == '') { // matching double slash after http:// etc
    return arr[2];
  } else {
    return arr[0];
  }
}


/**
 * We want to put all UI strings into the html pages of this extension to make future localization easier.
 * These are stored in hidden input fields. This function loads the element and pulls in the value from it.
 * @param elementid {string} id of the INPUT element
 */
function loadStringFromHtml(elementid) {
  var elem = document.getElementById(elementid);
  return (elem ? elem.value : '');
}

/**
 * Some sites (like apple) use different subdomains for changing the password.
 * This is crazy hard to do right. See: http://stackoverflow.com/questions/288810/get-the-subdomain-from-a-url
 * @param domain
 */
function normalizeDomain(domain) {
  var lowerCase = domain.toLowerCase();
  var parts = lowerCase.split('.');
  if (parts.length <= 2) {
    // simple case foo.com or internal server
    return domain;
  }

  if (parts.length == 4) { // we may have an ip
     // test for ip
    if (isNumber(parts[0]) && isNumber(parts[1]) && isNumber(parts[2]) && isNumber(parts[3]))
      return domain;
  }

  // figuring out the real domain is *hard* since there are no real rules for tld... no rules, just a long ass list.
  // see: http://mxr.mozilla.org/mozilla-central/source/netwerk/dns/effective_tld_names.dat?raw=1
  // This breaks for case where it's a 'naked' domain and has more than a single word tld (e.g. "bbc.co.uk").
  return parts.slice(1,parts.length).join('.'); // remove the first element.
}

/**
 *
 * @param domain {string} Domain from the url
 * @param email {string} email address or 'LESS_SECURE'
 * @param user_seed {string} Random string that we call the "fingerprint"
 * @param password_len {int} length of password.
 * @return {string}
 */
function genPassword(domain, email, user_seed) {
  var result_arr = [];  // build string as array, then join
  var seeding_str = normalizeDomain(domain) + email + user_seed;
  var seed = hash32(seeding_str);
  // if email is the magic 'LESS_SECURE', then load weak characterset and length
  var lookupDomain = (email != 'LESS_SECURE') ? domain : 'LESS_SECURE';
  var encoding_params = loadDataBasedOnDomain(lookupDomain);

  init_sequentialRandom(seed);

  // we want to make sure the "random" includes lower,upper,numbers,special
  result_arr.push(String.fromCharCode(sequentialRandomRange(97, 122))); // a-z
  result_arr.push(String.fromCharCode(sequentialRandomRange(48, 57))); // 0-9
  result_arr.push(String.fromCharCode(sequentialRandomRange(65, 90))); // A-Z

  var charset = encoding_params['charset'];
  var password_len = encoding_params['length'];
  
  for (var jj = 0; jj < 2000; jj++) { // avoid while loops. someone could have a really long string
    if (result_arr.length >= password_len) {
      break;
    }
    var cc = charset.charAt(sequentialRandomRange(0, charset.length));
    if (cc == result_arr[result_arr.length] && cc == result_arr[result_arr.length - 1]) {
      // this helps with apple's silly 'no 3 consecutive letters' rule.
      continue;
    }
    result_arr.push(cc);
  }

  return result_arr.join('');
}

/**
 * Do the best we can to make sure the password edit box is always "select all".
 * @param evt {event}
 */
function handleSelectAllText(evt) {
  if (this.nodeName != 'INPUT')
    return;

  switch (evt.type) {
    case 'onfocus':
      if (document.activeElement.id != this.id)
        this.select();
      break;

    case 'mousedown':
      this.select();
      break;

    case 'mouseup':
      {
        window.setTimeout(function() {
          function selectit(elem) {
            elem.select();
          }
          if (this)
            selectit(evt.target);
        }, 1);
      }
      break;
  }
}

/**
 * Build a single row of DOM and add it to parentPasswordList
 * @param password
 * @param email
 * @param parentPasswordList
 */
function addPasswordToUi(password, email, parentPasswordList, ii) {
  var row = document.createElement('tr');
  row.className = "password-tr";

  var column1 = document.createElement('td');
  column1.innerHTML = '<span class="passwordlistspan">' + email + ':</span>';
  column1.className = "account-td selectable";

  var column2 = document.createElement('td');
  column2.className = "password-td";

  var box = document.createElement('input');
  box.type = "text";
  box.readOnly = true;
  box.value = password;
  box.className = 'password-text';
  box.id = 'id'+ii;

  box.addEventListener('mousedown', handleSelectAllText);
  box.addEventListener('mouseup', handleSelectAllText);
  box.addEventListener('onfocus', handleSelectAllText);


  // now let's join everything up.
  column2.appendChild(box);
  row.appendChild(column1);
  row.appendChild(column2);
  parentPasswordList.appendChild(row);
}

/* opens the options.html page */
function settingsNav() {
  chrome.tabs.create({url:chrome.extension.getURL('options.html')}, function () {
    handleClosePopup();
  });
}

var gLastTabId = 0;
var gTimeoutId = 0;

/**
 * Removing. Not sure if this is needed.
 */
/*
function timeoutWatchForTabChange() {
  if (gTimeoutId == 0) return;

  chrome.tabs.getSelected(null, function (tab) {
    if (gLastTabId != 0 && tab.id != gLastTabId) {
      handleClosePopup();
      gTimeoutId = 0;
      gLastTabId = 0;
      return;
    }

    // check for tab to change again.
    gTimeoutId = window.setTimeout(timeoutWatchForTabChange, 200);
  });
}
*/

/*  closes our popup window */
function handleClosePopup() {
  // clear a possible pending timer
  if (gTimeoutId) {
    window.clearTimeout(gTimeoutId);
    gTimeoutId = 0;
  }

  // remove popup by selecting the tab
  chrome.tabs.getSelected(null, function (tab) {
    chrome.tabs.update(tab.id, { selected:true })
  });
}

/**
 * some websites put the login in the upper right where the dialog is. Get out of the way
 */
function handleDragTrigger(evt) {
  document.getElementById('toparea').removeEventListener('dragenter', handleDragTrigger, false);
  document.getElementById('mainbody').hidden = true;
}

function init() {
  chrome.tabs.getSelected(null, function (tab) {
    gLastTabId = tab.id;

    document.getElementById('closebox').addEventListener('click', handleClosePopup, false);
    document.getElementById('settings').addEventListener('click', settingsNav, false);
    // timeoutWatchForTabChange();

    if (tab.url.search(/chrome./i) == 0) { // startswith
      document.getElementById('domain').innerHTML = loadStringFromHtml('domainWarningMessageId');
      document.getElementById('message_default').hidden = true;
      document.getElementById('message_needswebsite').hidden = false;
      document.getElementById('passwordlist-div').hidden = true;
      return;
    }

    // we want a consistent domain name to use as the seed.
    var domain = normalizeDomain(parseDomain(tab.url));
    document.getElementById('domain').innerHTML = domain;

    var user_seed = localStorage["user_seed"] || '';
    var emails = (localStorage["emails"] || '').trim();
    if (user_seed == '' || emails == '') {
      document.getElementById('message_default').hidden = true;
      document.getElementById('message_needssetup').hidden = false;
      document.getElementById('passwordlist-div').hidden = true;
      return;
    }

    // provide passwords
    var parentPasswordList = document.getElementById('passwordlist-table');
    var user_emails = emails.split('\n');
    for (var ii = 0; ii < user_emails.length; ii++) {
      var email = user_emails[ii];
      var result = genPassword(domain, email, user_seed);
      // add each password to the page.
      addPasswordToUi(result, email, parentPasswordList, ii);
    }
    {
      // add a generic / easy password
      var easyemailUi = loadStringFromHtml('easyPasswordMessageId');
      var result = genPassword(domain, 'LESS_SECURE', user_seed);
      addPasswordToUi(result, easyemailUi, parentPasswordList, user_emails.length);
    }

    document.getElementById('toparea').addEventListener('dragleave', handleDragTrigger, false);

  });
}

window.onload = init;
